
#include "abcrypto.h"

#include "main.h"
#include "common.h"

int test_cipher(void)
{
	int ret = -1;
	uint32_t len;
	uint8_t *data = NULL;
	uint8_t *result[2] = { NULL };
	struct abcrypto_skcipher *acipher;
	struct abcrypto_skcipher *bcipher;

	void *acipher_ctx = NULL;
	void *bcipher_ctx = NULL;
	uint32_t local_loop = loop;
	uint8_t key[64] = { 0 };
	uint8_t iv[16];
	uint8_t tempiv[2][16];
	uint32_t diff = 0;
	uint32_t step = 16;
	uint32_t update_count;
	int i;

	if ((test_alg & 0xf) == ABCRYPTO_CIPHER_MODE_CTR ||
	    (test_alg & 0xf) == ABCRYPTO_CIPHER_MODE_CFB ||
	    (test_alg & 0xf) == ABCRYPTO_CIPHER_MODE_OFB) {
		step = 1;
	}

	/* set test_klen */
	switch (test_alg) {
	case ABCRYPTO_CIPHER_AES_128_ECB:
	case ABCRYPTO_CIPHER_AES_128_CBC:
	case ABCRYPTO_CIPHER_AES_128_CTR:
	case ABCRYPTO_CIPHER_AES_128_CFB:
	case ABCRYPTO_CIPHER_AES_128_OFB:
		test_klen = 16;
		break;
	case ABCRYPTO_CIPHER_AES_192_ECB:
	case ABCRYPTO_CIPHER_AES_192_CBC:
	case ABCRYPTO_CIPHER_AES_192_CTR:
	case ABCRYPTO_CIPHER_AES_192_CFB:
	case ABCRYPTO_CIPHER_AES_192_OFB:
		test_klen = 24;
		break;
	case ABCRYPTO_CIPHER_AES_256_ECB:
	case ABCRYPTO_CIPHER_AES_256_CBC:
	case ABCRYPTO_CIPHER_AES_256_CTR:
	case ABCRYPTO_CIPHER_AES_256_CFB:
	case ABCRYPTO_CIPHER_AES_256_OFB:
		test_klen = 32;
		break;
	case ABCRYPTO_CIPHER_SM4_ECB:
	case ABCRYPTO_CIPHER_SM4_CBC:
	case ABCRYPTO_CIPHER_SM4_CTR:
	case ABCRYPTO_CIPHER_SM4_CFB:
	case ABCRYPTO_CIPHER_SM4_OFB:
		test_klen = 16;
		break;
	default:
		fprintf(stderr, "Invalid cipher %s ", get_algo_name(test_alg));
		return -1;
	}

	acipher = a_instance->get_skcipher(test_alg);
	if (acipher == NULL) {
		fprintf(stderr, "Not find cipher %s implement in %s instance",
			get_algo_name(test_alg), a_instance->name);
		goto out;
	}

	bcipher = b_instance->get_skcipher(test_alg);
	if (bcipher == NULL) {
		fprintf(stderr, "Not find cipher %s implement in %s instance",
			get_algo_name(test_alg), b_instance->name);
		goto out;
	}

	data = malloc(elen);
	if (data == NULL)
		goto out;

	result[0] = malloc(elen);
	if (result[0] == NULL)
		goto out;

	result[1] = malloc(elen);
	if (result[1] == NULL)
		goto out;

	while (local_loop--) {
		get_rand(data, elen);
		get_rand(key, 64);
		get_rand(iv, 16);

		/* test init update final && digest */
		if (slen % step != 0) {
			slen = slen - (slen % step) + step;
		}

		if (step == 1)
			step = test_step;

		for (len = slen; len <= elen; len += step) {
			memcpy(tempiv[0], iv, 16);
			memcpy(tempiv[1], iv, 16);
			update_count = len / 16;

			/* update + update + udpate , diff */
			if (acipher->init_ctx(&acipher_ctx, key, test_klen,
					      tempiv[0], test_alg, true)) {
				fprintf(stderr, "Init ctx failed %s\n",
					a_instance->name);
				goto out;
			}

			if (bcipher->init_ctx(&bcipher_ctx, key, test_klen,
					      tempiv[1], test_alg, true)) {
				fprintf(stderr, "Init ctx failed %s\n",
					b_instance->name);
				acipher->free_ctx(acipher_ctx);
				goto out;
			}

			for (i = 0; i < update_count; i++) {
				acipher->encrypt(acipher_ctx, tempiv[0],
						 data + i * 16, 16,
						 result[0] + i * 16);
				bcipher->encrypt(bcipher_ctx, tempiv[1],
						 data + i * 16, 16,
						 result[1] + i * 16);
			}

			if (len % 16) {
				// printf("len %d\n", len);
				acipher->encrypt(acipher_ctx, tempiv[0],
						 data + update_count * 16,
						 len % 16,
						 result[0] + update_count * 16);
				bcipher->encrypt(bcipher_ctx, tempiv[1],
						 data + update_count * 16,
						 len % 16,
						 result[1] + update_count * 16);
			}

			acipher->free_ctx(acipher_ctx);
			bcipher->free_ctx(bcipher_ctx);

			/* a init update final, b init updata final */
			if (memcmp(result[0], result[1], len)) {
				fprintf(stderr,
					"Dismatch results[uu] len %u %s\n", len,
					get_algo_name(test_alg));
				print_buf(a_instance->name, result[0], 128);
				print_buf(b_instance->name, result[1], 128);
				print_buf("data", data, len);
				print_buf("key", key, test_klen);
				print_buf("iv", iv, 16);
				diff++;
				if (!test_skip)
					goto out;
				else
					continue;
			}

			memcpy(tempiv[0], iv, 16);
			memset(result[0], 0, len);

			/* test once */
			acipher->once_encrypt(key, tempiv[0], data, result[0],
					      len, test_alg);

			/* a init update final, b init updata final */
			if (memcmp(result[0], result[1], len)) {
				fprintf(stderr,
					"Dismatch results[ou] len %u %s\n", len,
					get_algo_name(test_alg));
				print_buf(a_instance->name, result[0], 128);
				print_buf(b_instance->name, result[1], 128);
				print_buf("data", data, len);
				print_buf("key", key, test_klen);
				print_buf("iv", iv, 16);
				diff++;
				if (!test_skip)
					goto out;
				else
					continue;
			}

			memcpy(tempiv[1], iv, 16);
			memset(result[1], 0, len);

			bcipher->once_encrypt(key, tempiv[1], data, result[1],
					      len, test_alg);

			/* a init update final, b init updata final */
			if (memcmp(result[0], result[1], len)) {
				fprintf(stderr,
					"Dismatch results[oo] len %u %s\n", len,
					get_algo_name(test_alg));
				print_buf(a_instance->name, result[0], 128);
				print_buf(b_instance->name, result[1], 128);
				print_buf("data", data, len);
				print_buf("key", key, test_klen);
				print_buf("iv", iv, 16);
				diff++;
				if (!test_skip)
					goto out;
				else
					continue;
			}

			/* a decrypt? */
			memcpy(tempiv[0], iv, 16);
			memset(result[1], 0, len);
			/* update + update + udpate , diff */
			if (acipher->init_ctx(&acipher_ctx, key, test_klen,
					      tempiv[0], test_alg, false)) {
				fprintf(stderr, "Init ctx failed %s\n",
					a_instance->name);
				goto out;
			}

			for (i = 0; i < update_count; i++) {
				acipher->decrypt(acipher_ctx, tempiv[0],
						 result[0] + i * 16, 16,
						 result[1] + i * 16);
			}

			if (len % 16) {
				// printf("len %d\n", len);
				acipher->decrypt(acipher_ctx, tempiv[0],
						 result[0] + update_count * 16,
						 len % 16,
						 result[1] + update_count * 16);
			}

			acipher->free_ctx(acipher_ctx);
			if (memcmp(data, result[1], len)) {
				fprintf(stderr,
					"Dismatch decrypt results[u] len %u %s\n",
					len, get_algo_name(test_alg));
				print_buf(a_instance->name, data, len);
				print_buf(b_instance->name, result[1], len);
				print_buf("cipher", result[0], len);
				print_buf("key", key, test_klen);
				print_buf("iv", iv, 16);
				diff++;
				if (!test_skip)
					goto out;
				else
					continue;
			}

			/* a once_decrypt */
			memcpy(tempiv[0], iv, 16);
			memset(result[1], 0, len);
			acipher->once_decrypt(key, tempiv[0], result[0],
					      result[1], len, test_alg);
			if (memcmp(data, result[1], len)) {
				fprintf(stderr,
					"Dismatch decrypt results[o] len %u %s\n",
					len, get_algo_name(test_alg));
				print_buf(a_instance->name, data, len);
				print_buf(b_instance->name, result[1], len);
				print_buf("cipher", result[0], len);
				print_buf("key", key, test_klen);
				print_buf("iv", iv, 16);
				diff++;
				if (!test_skip)
					goto out;
				else
					continue;
			}
		}
	}

	ret = 0;

out:
	if (diff) {
		printf("Test alg %s failed,  slen %u, elen %u, loop %u, diff %u\n",
		       get_algo_name(test_alg), slen, elen, loop, diff);
	} else
		printf("Test alg %s success, slen %u, elen %u, loop %u\n",
		       get_algo_name(test_alg), slen, elen, loop);

	if (data)
		free(data);

	if (result[1])
		free(result[1]);

	if (result[0])
		free(result[0]);

	return ret;
}